package xin.marcher.wind.process.model;

import lombok.Data;
import xin.marcher.wind.process.instance.ProcessorInstanceCreator;
import xin.marcher.wind.process.node.BizConfig;
import xin.marcher.wind.process.node.ProcessorDefinition;
import xin.marcher.wind.process.node.ProcessorNode;
import xin.marcher.wind.process.processor.Processor;
import xin.marcher.wind.process.utils.ProcessorUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 表示解析出来的一个流程配置信息
 *
 * @author marcher
 */
@Data
public class ProcessModel {

    /**
     * 名称
     */
    public String name;

    /**
     * 流程节点
     */
    public Map<String, ProcessNodeModel> nodes = new HashMap<>();

    /**
     * 关联的业务
     */
    public Set<BizConfigModel> bizConfigs = new HashSet<>();

    /**
     * 添加一个节点
     *
     * @param processNodeModel 节点配置
     */
    public void addNode(ProcessNodeModel processNodeModel) {
        if (nodes.containsKey(processNodeModel.getName())) {
            throw new IllegalArgumentException("同一个流程不能定义多个相同 id 的节点");
        }
        nodes.put(processNodeModel.getName(), processNodeModel);
    }

    /**
     * 添加一个业务
     *
     * @param bizConfigModel 关联的业务
     */
    public void addBizConfig(BizConfigModel bizConfigModel) {
        bizConfigs.add(bizConfigModel);
    }

    /**
     * 检查配置是否合法：
     *
     * 1、className 是否合法
     * 2、上下节点依赖是否正确
     * 3、是否仅有一个开始节点
     */
    public void check() {
        int startNode = 0;
        for (ProcessNodeModel processNodeModel : nodes.values()) {
            String className = processNodeModel.getClassName();
            try {
                // 先去判断一下每个流程节点的 class 是否可以加载，是否是有效的 class
                Class.forName(className);
            } catch (Throwable e) {
                throw new IllegalArgumentException("无法加载节点[" + processNodeModel.getName() + "]的类：" + className);
            }

            String nextNode = processNodeModel.getNextNode();
            if (nextNode != null) {
                String[] nextNodes = nextNode.split(",");
                // next 指向的 node 节点的 names，必须是在你的流程定义里定义的其他的 node name
                for (String nodeName : nextNodes) {
                    if (!nodes.containsKey(nodeName)) {
                        throw new IllegalArgumentException("节点[name=" + nodeName + "]不存在");
                    }
                }
            }

            // 如果说 process node 设置了 begin=true，就把 startNode 数量 ++
            if (processNodeModel.getBegin()) {
                startNode++;
            }
        }

        // 如果设置了 begin=true 的节点数量大于 了1，此时不等于 1，或者是小于 1，没有设置 begin 节点
        // 这两种情况都是不对的，有且仅有一个 begin 节点
        if (startNode != 1) {
            throw new IllegalArgumentException("不合法的流程，每个流程只能有一个开始节点");
        }
    }

    public ProcessorDefinition build(ProcessorInstanceCreator creator) throws Exception {
        Map<String, ProcessorNode> processorNodeMap = new HashMap<>();
        ProcessorDefinition processorDefinition = new ProcessorDefinition();
        processorDefinition.setName(name);

        // 第一次循环，将所有的 processNode 转化为 processorNode 实例，并保存在集合中，并将第一个节点放入 processorDefinition
        for (ProcessNodeModel processNodeModel : nodes.values()) {
            String className = processNodeModel.getClassName();
            // 根据流程节点的 class，以及流程节点的 name，在这里使用流程节点构造器，去构造流程节点对应的 Processor
            // 这个 bea n实例，他就是业务代码里自己定义的 spring bean，强转为接口类型
            Processor processor = creator.newInstance(className, processNodeModel.getName());
            // ProcessorNode代表了一个可以运行的流程节点
            ProcessorNode processorNode = new ProcessorNode();
            processorNode.setProcessor(processor);
            processorNode.setName(processNodeModel.getName());
            // 如果说流程节点数据模型里的 begin 属性是 true，此时就把 ProcessorNode 设置为 ProcessorDefinition 里的第一个流程节点
            if (processNodeModel.getBegin()) {
                processorDefinition.setFirst(processorNode);
            }
            // 方法调用模式，sync 还是 async，设置到 ProcessorNode 里去
            processorNode.setInvokeMethod(processNodeModel.getInvokeMethod());
            // 到此为止，就把所有的流程节点，都从流程节点定义数据模型，转换为了 ProcessorNode可 运行的流程节点
            processorNodeMap.put(processNodeModel.getName(), processorNode);
        }

        // 第二次循环，将所有节点建立关联关系
        // 遍历所有的可运行的 ProcessorNode，根据每个 ProcessNode 他的一个 next 关系
        // 去进行 ProcessorNode 之间的依赖关系
        for (ProcessorNode processNode : processorNodeMap.values()) {
            String nextNodeStr = nodes.get(processNode.getName()).getNextNode();
            if (nextNodeStr == null) {
                continue;
            }
            String[] nextNodes = nextNodeStr.split(",");
            for (String nextNode : nextNodes) {
                processNode.addNextNode(processorNodeMap.get(nextNode));
            }
        }

        // 设置关联的业务
        for(BizConfigModel model : bizConfigs) {
            processorDefinition.addBizConfig(new BizConfig(model.getName(),model.getBusinessIdentifier(),model.getOrderType()));
        }

        // 要是创建出了环形流程就报错
        if (ProcessorUtils.hasRing(processorDefinition.getFirst())) {
            throw new IllegalArgumentException("Processor chain exists ring.");
        }

        // 可运行的一个流程就搞好了
        return processorDefinition;
    }
}
